import Vue from 'vue';
import scrollbarWidth from 'element-ui/src/utils/scrollbar-width';
import {parseHeight} from './util';

class TableLayout {
    constructor(options) {
        this.observers = [];
        this.table = null;
        this.store = null;
        this.columns = null;
        this.fit = true;
        this.showHeader = true;

        this.height = null;
        this.scrollX = false;
        this.scrollY = false;
        this.bodyWidth = null;
        this.fixedWidth = null;
        this.rightFixedWidth = null;
        this.tableHeight = null;
        this.headerHeight = 44; // Table Header Height
        this.appendHeight = 0; // Append Slot Height
        this.footerHeight = 44; // Table Footer Height
        this.viewportHeight = null; // Table Height - Scroll Bar Height
        this.bodyHeight = null; // Table Height - Table Header Height
        this.fixedBodyHeight = null; // Table Height - Table Header Height - Scroll Bar Height
        this.gutterWidth = scrollbarWidth();

        for (let name in options) {
            if (options.hasOwnProperty(name)) {
                this[name] = options[name];
            }
        }

        if (!this.table) {
            throw new Error('table is required for Table Layout');
        }
        if (!this.store) {
            throw new Error('store is required for Table Layout');
        }
    }

    updateScrollY() {
        const height = this.height;
        if (height === null) return false;
        const bodyWrapper = this.table.bodyWrapper;
        if (this.table.$el && bodyWrapper) {
            const body = bodyWrapper.querySelector('.el-table__body');
            const prevScrollY = this.scrollY;
            const scrollY = body.offsetHeight > this.bodyHeight;
            this.scrollY = scrollY;
            return prevScrollY !== scrollY;
        }
        return false;
    }

    setHeight(value, prop = 'height') {
        if (Vue.prototype.$isServer) return;
        const el = this.table.$el;
        value = parseHeight(value);
        this.height = value;

        if (!el && (value || value === 0)) return Vue.nextTick(() => this.setHeight(value, prop));

        if (typeof value === 'number') {
            el.style[prop] = value + 'px';
            this.updateElsHeight();
        } else if (typeof value === 'string') {
            el.style[prop] = value;
            this.updateElsHeight();
        }
    }

    setMaxHeight(value) {
        this.setHeight(value, 'max-height');
    }

    getFlattenColumns() {
        const flattenColumns = [];
        const columns = this.table.columns;
        columns.forEach((column) => {
            if (column.isColumnGroup) {
                flattenColumns.push.apply(flattenColumns, column.columns);
            } else {
                flattenColumns.push(column);
            }
        });

        return flattenColumns;
    }

    updateElsHeight() {
        if (!this.table.$ready) return Vue.nextTick(() => this.updateElsHeight());
        const {headerWrapper, appendWrapper, footerWrapper} = this.table.$refs;
        this.appendHeight = appendWrapper ? appendWrapper.offsetHeight : 0;

        if (this.showHeader && !headerWrapper) return;

        // fix issue (https://github.com/ElemeFE/element/pull/16956)
        const headerTrElm = headerWrapper ? headerWrapper.querySelector('.el-table__header tr') : null;
        const noneHeader = this.headerDisplayNone(headerTrElm);

        const headerHeight = this.headerHeight = !this.showHeader ? 0 : headerWrapper.offsetHeight;
        if (this.showHeader && !noneHeader && headerWrapper.offsetWidth > 0 && (this.table.columns || []).length > 0 && headerHeight < 2) {
            return Vue.nextTick(() => this.updateElsHeight());
        }
        const tableHeight = this.tableHeight = this.table.$el.clientHeight;
        const footerHeight = this.footerHeight = footerWrapper ? footerWrapper.offsetHeight : 0;
        if (this.height !== null) {
            this.bodyHeight = tableHeight - headerHeight - footerHeight + (footerWrapper ? 1 : 0);
        }
        this.fixedBodyHeight = this.scrollX ? (this.bodyHeight - this.gutterWidth) : this.bodyHeight;

        const noData = !(this.store.states.data && this.store.states.data.length);
        this.viewportHeight = this.scrollX ? tableHeight - (noData ? 0 : this.gutterWidth) : tableHeight;

        this.updateScrollY();
        this.notifyObservers('scrollable');
    }

    headerDisplayNone(elm) {
        if (!elm) return true;
        let headerChild = elm;
        while (headerChild.tagName !== 'DIV') {
            if (getComputedStyle(headerChild).display === 'none') {
                return true;
            }
            headerChild = headerChild.parentElement;
        }
        return false;
    }

    updateColumnsWidth() {
        if (Vue.prototype.$isServer) return;
        const fit = this.fit;
        const bodyWidth = this.table.$el.clientWidth;
        let bodyMinWidth = 0;

        const flattenColumns = this.getFlattenColumns();
        let flexColumns = flattenColumns.filter((column) => typeof column.width !== 'number');

        flattenColumns.forEach((column) => { // Clean those columns whose width changed from flex to unflex
            if (typeof column.width === 'number' && column.realWidth) column.realWidth = null;
        });

        if (flexColumns.length > 0 && fit) {
            flattenColumns.forEach((column) => {
                bodyMinWidth += column.width || column.minWidth || 80;
            });

            const scrollYWidth = this.scrollY ? this.gutterWidth : 0;

            if (bodyMinWidth <= bodyWidth - scrollYWidth) { // DON'T HAVE SCROLL BAR
                this.scrollX = false;

                const totalFlexWidth = bodyWidth - scrollYWidth - bodyMinWidth;

                if (flexColumns.length === 1) {
                    flexColumns[0].realWidth = (flexColumns[0].minWidth || 80) + totalFlexWidth;
                } else {
                    const allColumnsWidth = flexColumns.reduce((prev, column) => prev + (column.minWidth || 80), 0);
                    const flexWidthPerPixel = totalFlexWidth / allColumnsWidth;
                    let noneFirstWidth = 0;

                    flexColumns.forEach((column, index) => {
                        if (index === 0) return;
                        const flexWidth = Math.floor((column.minWidth || 80) * flexWidthPerPixel);
                        noneFirstWidth += flexWidth;
                        column.realWidth = (column.minWidth || 80) + flexWidth;
                    });

                    flexColumns[0].realWidth = (flexColumns[0].minWidth || 80) + totalFlexWidth - noneFirstWidth;
                }
            } else { // HAVE HORIZONTAL SCROLL BAR
                this.scrollX = true;
                flexColumns.forEach(function (column) {
                    column.realWidth = column.minWidth;
                });
            }

            this.bodyWidth = Math.max(bodyMinWidth, bodyWidth);
            this.table.resizeState.width = this.bodyWidth;
        } else {
            flattenColumns.forEach((column) => {
                if (!column.width && !column.minWidth) {
                    column.realWidth = 80;
                } else {
                    column.realWidth = column.width || column.minWidth;
                }

                bodyMinWidth += column.realWidth;
            });
            this.scrollX = bodyMinWidth > bodyWidth;

            this.bodyWidth = bodyMinWidth;
        }

        const fixedColumns = this.store.states.fixedColumns;

        if (fixedColumns.length > 0) {
            let fixedWidth = 0;
            fixedColumns.forEach(function (column) {
                fixedWidth += column.realWidth || column.width;
            });

            this.fixedWidth = fixedWidth;
        }

        const rightFixedColumns = this.store.states.rightFixedColumns;
        if (rightFixedColumns.length > 0) {
            let rightFixedWidth = 0;
            rightFixedColumns.forEach(function (column) {
                rightFixedWidth += column.realWidth || column.width;
            });

            this.rightFixedWidth = rightFixedWidth;
        }

        this.notifyObservers('columns');
    }

    addObserver(observer) {
        this.observers.push(observer);
    }

    removeObserver(observer) {
        const index = this.observers.indexOf(observer);
        if (index !== -1) {
            this.observers.splice(index, 1);
        }
    }

    notifyObservers(event) {
        const observers = this.observers;
        observers.forEach((observer) => {
            switch (event) {
                case 'columns':
                    observer.onColumnsChange(this);
                    break;
                case 'scrollable':
                    observer.onScrollableChange(this);
                    break;
                default:
                    throw new Error(`Table Layout don't have event ${event}.`);
            }
        });
    }
}

export default TableLayout;
